﻿
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System.Linq.Expressions;
using wwl.Contract.Interfaces.MultiTenancy;

namespace wwl.Infrastructure.Persistence.Interceptors
{
  /// <summary>
  /// EF Core 租户数据拦截器。
  /// 自动为实现了 <see cref="ITenantAware"/> 的实体处理多租户逻辑：
  /// 1. 保存时：自动填充 TenantId，并防止跨租户更新。
  /// 2. 查询时：自动附加 TenantId 过滤条件。
  /// </summary>
  public class TenantDataInterceptor : SaveChangesInterceptor, IQueryExpressionInterceptor
  {
    private readonly ITenantContext _tenantContext;

    /// <summary>
    /// 初始化 <see cref="TenantDataInterceptor"/> 的新实例。
    /// </summary>
    /// <param name="tenantContext">租户上下文服务，用于获取当前租户信息。</param>
    /// <exception cref="ArgumentNullException">当 <paramref name="tenantContext"/> 为 null 时抛出。</exception>
    public TenantDataInterceptor(ITenantContext tenantContext)
    {
      _tenantContext = tenantContext ??
          throw new ArgumentNullException(nameof(tenantContext));
    }

    #region SaveChanges 拦截 (新增/更新)

    /// <summary>
    /// 在保存更改之前拦截，处理租户ID的自动填充和验证。
    /// </summary>
    public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
    {
      ProcessSavingChanges(eventData.Context);
      return base.SavingChanges(eventData, result);
    }

    /// <summary>
    /// 在异步保存更改之前拦截，处理租户ID的自动填充和验证。
    /// </summary>
    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
    {
      ProcessSavingChanges(eventData.Context);
      return base.SavingChangesAsync(eventData, result, cancellationToken);
    }

    /// <summary>
    /// 处理保存逻辑的核心方法。
    /// </summary>
    /// <param name="context">当前的 DbContext。</param>
    private void ProcessSavingChanges(DbContext? context)
    {
      if (context == null || _tenantContext.IsSystemAdmin)
      {
        return; // 系统管理员跳过所有租户验证和填充逻辑
      }

      var currentTenantId = _tenantContext.TenantId;
      _tenantContext.EnsureTenantScope(); // 确保存在租户上下文

      // 处理新增实体
      foreach (var entry in context.ChangeTracker.Entries<ITenantAware>())
      {
        if (entry.State == EntityState.Added)
        {
          // 如果实体没有 TenantId，则自动填充
          if (string.IsNullOrEmpty(entry.Entity.TenantId))
          {
            entry.Entity.TenantId = currentTenantId;
          }
          // 如果实体有 TenantId，但与当前租户不匹配，则抛出异常
          else if (entry.Entity.TenantId != currentTenantId)
          {
            throw new InvalidOperationException(
                $"尝试创建一个属于租户 '{entry.Entity.TenantId}' 的实体，但当前上下文租户为 '{currentTenantId}'。");
          }
        }
        // 处理更新实体
        else if (entry.State == EntityState.Modified)
        {
          // 检查原始 TenantId 是否与当前租户匹配
          var originalTenantId = entry.OriginalValues[nameof(ITenantAware.TenantId)] as string;
          if (originalTenantId != currentTenantId)
          {
            throw new InvalidOperationException(
                $"尝试更新一个属于租户 '{originalTenantId}' 的实体，但当前上下文租户为 '{currentTenantId}'。");
          }

          // 防止 TenantId 被修改
          if (entry.Property(nameof(ITenantAware.TenantId)).IsModified)
          {
            entry.Property(nameof(ITenantAware.TenantId)).IsModified = false;
          }
        }
      }
    }

    #endregion

    #region Query 拦截 (查询)

    /// <summary>
    /// 在查询表达式执行前拦截，自动为 <see cref="ITenantAware"/> 实体添加 TenantId 过滤。
    /// </summary>
    public Expression InterceptQuery(Expression queryExpression)
    {
      // 系统管理员可以看到所有数据，不进行过滤
      if (_tenantContext.IsSystemAdmin)
      {
        return queryExpression;
      }

      var currentTenantId = _tenantContext.TenantId;
      // 如果没有租户上下文（例如在某些后台任务中），则返回空查询
      if (string.IsNullOrEmpty(currentTenantId))
      {
        return CreateEmptyQueryExpression(queryExpression);
      }

      // 使用表达式访问器来修改查询树
      var visitor = new TenantQueryExpressionVisitor(currentTenantId);
      return visitor.Visit(queryExpression);
    }

    /// <summary>
    /// 创建一个返回空结果的查询表达式，用于在没有租户上下文时确保数据安全。
    /// </summary>
    private static Expression CreateEmptyQueryExpression(Expression queryExpression)
    {
      var elementType = queryExpression.Type.GetGenericArguments().FirstOrDefault()
          ?? throw new InvalidOperationException("无法确定查询元素的类型。");

      var emptyMethod = typeof(Enumerable).GetMethod("Empty", Type.EmptyTypes)!
          .MakeGenericMethod(elementType);

      return Expression.Call(emptyMethod);
    }

    #endregion
  }

  /// <summary>
  /// 一个表达式访问器，用于遍历并修改查询表达式树，动态添加 TenantId 过滤条件。
  /// </summary>
  internal class TenantQueryExpressionVisitor : ExpressionVisitor
  {
    private readonly string _tenantId;

    /// <summary>
    /// 初始化 <see cref="TenantQueryExpressionVisitor"/>。
    /// </summary>
    /// <param name="tenantId">当前租户ID，用于构建过滤条件。</param>
    public TenantQueryExpressionVisitor(string tenantId)
    {
      _tenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId));
    }

    /// <summary>
    /// 重写对方法调用表达式的访问，主要是为了处理 Where 方法。
    /// </summary>
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
      // 只处理 Queryable.Where 方法
      if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == "Where")
      {
        var sourceExpression = node.Arguments[0];
        var predicateExpression = node.Arguments[1];

        // 检查查询的源是否是 ITenantAware 类型
        var sourceElementType = sourceExpression.Type.GetGenericArguments().FirstOrDefault();
        if (sourceElementType != null && typeof(ITenantAware).IsAssignableFrom(sourceElementType))
        {
          // 构建一个新的、包含 TenantId 过滤的表达式
          var combinedPredicate = CombinePredicates(sourceElementType, predicateExpression);

          // 创建一个新的 Where 方法调用，使用组合后的表达式
          var newWhereCall = Expression.Call(
              typeof(Queryable),
              "Where",
              new[] { sourceElementType },
              sourceExpression,
              combinedPredicate);

          return newWhereCall;
        }
      }
      // 如果不是 Where 方法，或者源不是 ITenantAware，则继续遍历
      return base.VisitMethodCall(node);
    }

    /// <summary>
    /// 将原始的查询条件和我们的 TenantId 过滤条件组合起来。
    /// </summary>
    /// <param name="elementType">查询元素的类型。</param>
    /// <param name="originalPredicate">原始的查询条件表达式。</param>
    /// <returns>一个新的、组合了两个条件的表达式。</returns>
    private Expression CombinePredicates(Type elementType, Expression originalPredicate)
    {
      // 原始 Lambda 参数 (e)
      var originalParameter = ((LambdaExpression)((UnaryExpression)originalPredicate).Operand).Parameters[0];

      // 创建 TenantId == _tenantId 的表达式 (e => e.TenantId == tenantId)
      var tenantIdProperty = Expression.Property(originalParameter, nameof(ITenantAware.TenantId));
      var tenantIdConstant = Expression.Constant(_tenantId);
      var tenantPredicate = Expression.Equal(tenantIdProperty, tenantIdConstant);

      // 获取原始的 Lambda 表达式体 (e => [OriginalCondition])
      var originalBody = ((LambdaExpression)((UnaryExpression)originalPredicate).Operand).Body;

      // 组合两个条件 (e => [OriginalCondition] && e.TenantId == tenantId)
      var combinedBody = Expression.AndAlso(originalBody, tenantPredicate);

      // 创建一个新的 Lambda 表达式
      return Expression.Lambda(combinedBody, originalParameter);
    }
  }
}